/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.dotaznikserver;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import java.util.prefs.Preferences;

/**
 *
 * @author Jan Hamáček
 */
public class Connection {
    
    /**
     * Nastaví cílový počítač.
     * @param target Adresa cílového počítače.
     */
    public Connection(InetAddress target){
        this.target = target;
    }
    
    public InetAddress getInetAddress(){
        return target;
    }
    
    
    /**
     * Odešle přes toto spojení řetězec str a připraví se ke čtení ze spojení.
     * @param  str řetězec k odeslání.
     */
    public void writeLine(String str) throws IOException{
        Preferences pref = Preferences.userNodeForPackage(UI.class);
        
        try{
            if(connection == null || !connection.isConnected() || connection.isClosed()){
                connection = new Socket(target, pref.getInt("Port", 7475));
                
                
            }
            
            try{
                if(listenThread == null || !listenThread.isAlive()){
                    listenThread = new ListenThread();
                    listenThread.start();
                }

                BufferedWriter out = new BufferedWriter(
                        new OutputStreamWriter(
                        connection.getOutputStream(), charset));
                
                out.write(str);
                out.newLine();
                out.flush();
                this.alive = true;
            }catch(IOException ex){
                connection.close();
                connection = null;
                throw ex;
            }
        }catch(IOException e){
            this.alive = false;
            throw e;
        }
    }
    
    /**
     * Vrátí jméno spojení dříve nastavené přes 
     * {@link Connection#setName(java.lang.String) setName()}.
     * Nemusí být unikátní, bude použito pro označení v GUI, případně v logu.
     * @return Jméno spojení.
     */
    @Override
    public String toString(){
        if(this.alive){
            return getName();
        }else{
            return "(offline) "+getName();
        }
    }
    
    private String getName(){
        if(this.name != null && !"".equals(this.name)){
            return this.name;
        }
        return this.target.getHostName();
    }
    
    /**
     * Nastaví jméno spojení. Nemusí být unikátní, bude použito pro označení v GUI,
     * případně v logu.
     * @param name Jméno spojení.
     */
    public void setName(String name){
        PropertyChangeEvent ev = new PropertyChangeEvent(this, "name", this.name, name);
        this.name = name;
        firePropertyChange(ev);
    }
    
    /**
     * Přidá listener, který se spustí při příjmu řádku.
     * @param i metoda zabalená do interface DataRecievedListener.
     */
    public void addDataRecievedListener(DataRecievedListener i){
        this.dataListeners.add(i);
    }
    
    
    private class ListenThread extends Thread {

        @Override
        public void run(){
            try{
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(
                        connection.getInputStream(), charset));
                String line;
                while((line = in.readLine()) != null){
                    for(DataRecievedListener i:dataListeners){
                        i.dataRecieved(Connection.this, line);
                    }
                }
            }catch(IOException e){
                
            }
        }
    }
    
    /**
     * Přidá {@link java.beans.PropertyChangeListener propertyChangeListener}.
     * V současné době je volán jen při změně atributu name.
     * @param l 
     */
    public void addPropertyChangeListener(PropertyChangeListener l){
        propListeners.add(l);
    }
    
    /**
     * Vrací hodnotu, jestli je toto připojení funkční.
     * @return true, pokud o něm ještě nebylo zjištěno, že není funkční.
     * false, pokud se už někdy nepodařilo připojit.
     */
    public boolean isAlive(){
        return this.alive;
    }
    
    protected void firePropertyChange(PropertyChangeEvent ev){
        for(PropertyChangeListener l:propListeners){
            l.propertyChange(ev);
        }
    }
    
    private void setAlive(boolean alive){
        if(alive == this.alive){
            return;
        }
        PropertyChangeEvent ev = new PropertyChangeEvent(this, "alive", this.alive, alive);
        this.alive = alive;
        firePropertyChange(ev);
    }
    
    
    private ListenThread listenThread = null;
    private Socket connection = null;
    private InetAddress target = null;
    private String name = null;
    private boolean alive = true;
    
    private Set<PropertyChangeListener> propListeners = new HashSet<>();
    private Set<DataRecievedListener> dataListeners = new HashSet<>();
    
    /** Znaková sada použitá k síťovému přenosu. Musí být stejná na serveru i na klientovi */
    private final static Charset charset = Charset.forName("UTF-8");
}
